﻿using System;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using X = Adjunct.Framework.Nuspec.XNames.Package;

namespace Adjunct.Framework.Nuspec
{
	/// <summary>
	/// 
	/// </summary>
	/// <remarks></remarks>
	public class NuspecManifest : IXmlSerializable
	{
		private NuspecIncludes _includes;
		private NuspecMetadata _metadata;

		/// <summary>
		/// Initializes a new instance of the <see cref="NuspecManifest"/> class.
		/// </summary>
		/// <param name="metadata">The metadata.</param>
		/// <param name="includes">The includes.</param>
		/// <remarks></remarks>
		public NuspecManifest(NuspecMetadata metadata = null, NuspecIncludes includes = null)
		{
			_metadata = metadata;
			_includes = includes;
		}

		/// <summary>
		/// Gets the includes.
		/// </summary>
		/// <remarks></remarks>
		public NuspecIncludes Includes
		{
			get { return _includes ?? (_includes = new NuspecIncludes()); }
			private set { _includes = value; }
		}

		/// <summary>
		/// Gets the metadata.
		/// </summary>
		/// <remarks></remarks>
		public NuspecMetadata Metadata
		{
			get { return _metadata ?? (_metadata = new NuspecMetadata()); }
			private set { _metadata = value; }
		}

		/// <summary>
		/// Clones this instance.
		/// </summary>
		/// <returns></returns>
		/// <remarks></remarks>
		public NuspecManifest Clone()
		{
			return new NuspecManifest(_metadata.Clone(), _includes.Clone());
		}

		/// <summary>
		/// Saves the specified path.
		/// </summary>
		/// <param name="path">The path.</param>
		/// <param name="includeFiles">if set to <c>true</c> [include files].</param>
		/// <remarks></remarks>
		public void Save(string path, bool includeFiles = true)
		{
			if (string.IsNullOrWhiteSpace(path))
			{
				throw new Exception("Nuspec.Save(): No path specified.");
			}
			XDocument doc = new XDocument(ToXElement(includeFiles));
			doc.Save(path);
		}

		/// <summary>
		/// Toes the X element.
		/// </summary>
		/// <param name="includeFiles">if set to <c>true</c> [include files].</param>
		/// <returns></returns>
		/// <remarks></remarks>
		public XElement ToXElement(bool includeFiles = true)
		{
			return new XElement(X.ElementName, (XElement)Metadata, includeFiles ? (XElement)Includes : null);
		}

		/// <summary>
		/// This method is reserved and should not be used. When implementing the IXmlSerializable interface, 
		/// you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom 
		/// schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the  object that 
		/// is produced by the  <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> 
		/// method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> 
		/// method.
		/// </returns>
		/// <inheritdoc/>
		/// <remarks></remarks>
		XmlSchema IXmlSerializable.GetSchema()
		{
			return null;
		}

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is hydrated.</param>
		/// <inheritdoc/>
		/// <remarks></remarks>
		void IXmlSerializable.ReadXml(XmlReader reader)
		{
			XElement element = XElement.Load(reader);
			Load(element, this);
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized.</param>
		/// <inheritdoc/>
		/// <remarks></remarks>
		void IXmlSerializable.WriteXml(XmlWriter writer)
		{
			ToXElement().WriteAsCurrentElementTo(writer);
		}

		/// <summary>
		/// Loads the specified file path.
		/// </summary>
		/// <param name="filePath">The file path.</param>
		/// <returns></returns>
		/// <remarks></remarks>
		public static NuspecManifest Load(string filePath)
		{
			XDocument doc = XDocument.Load(filePath);
			return Load(doc.Root);
		}

		/// <summary>
		/// Loads the specified element.
		/// </summary>
		/// <param name="element">The element.</param>
		/// <param name="loadThis">The load this.</param>
		/// <returns></returns>
		/// <remarks></remarks>
		public static NuspecManifest Load(XElement element, NuspecManifest loadThis = null)
		{
			Guard.NotNull(element, () => element);
			if (element.Name.Namespace != "")
			{
				element.ChangeNamespaceTo("");
			}
			element.EnsureElementNameIs(X.ElementName);
			if (loadThis == null)
			{
				loadThis = new NuspecManifest();
			}
			loadThis.Metadata = (NuspecMetadata)element.EnsureElement(X.Metadata.ElementName);
			loadThis.Includes = (NuspecIncludes)element.Element(X.Files.ElementName);
			return loadThis;
		}

		/// <summary>
		/// Performs an explicit conversion from <see cref="NuspecManifest"/> to <see cref="System.Xml.Linq.XElement"/>.
		/// </summary>
		/// <param name="nuspecManifest">The nuspec.</param>
		/// <returns>The result of the conversion.</returns>
		/// <remarks></remarks>
		public static explicit operator XElement(NuspecManifest nuspecManifest)
		{
			return (nuspecManifest == null) ? null : nuspecManifest.ToXElement();
		}

		/// <summary>
		/// Performs an explicit conversion from <see cref="System.Xml.Linq.XElement"/> to <see cref="NuspecManifest"/>.
		/// </summary>
		/// <param name="element">The element.</param>
		/// <returns>The result of the conversion.</returns>
		/// <remarks></remarks>
		public static explicit operator NuspecManifest(XElement element)
		{
			return (element == null) ? null : Load(element);
		}
	}
}